#include "linux/irqdesc.h"
#include "linux/err.h"
#include "asm/ptrace.h"
#include "linux/io.h"
#include "linux/init.h"
#include "linux/of.h"
#include "linux/errno.h"
#include "linux/irq.h"
#include "linux/irqchip.h"
#include "linux/compiler.h"
#include "linux/of_address.h"
#include "linux/irqdomain.h"
#include "linux/irqchip/arm-gic.h"

#include "irq-gic-common.h"

struct irq_chip gic_chip;

#define MAX_GIC_NR	1
#define NR_GIC_CPU_IF 8
static unsigned char gic_cpu_map[NR_GIC_CPU_IF];

#define gic_data_dist_base(d)	((d)->dist_base.common_base)
#define gic_data_cpu_base(d)	((d)->cpu_base.common_base)
#define gic_set_base_accessor(d, f)

union gic_base {
	void __iomem *common_base;
	//void __percpu * __iomem *percpu_base;
};

struct gic_chip_data
{
    union gic_base dist_base;
    union gic_base cpu_base;
    struct irq_domain *domain;
    struct irq_chip chip;
    unsigned int gic_irqs;
};

static struct gic_chip_data gic_data[MAX_GIC_NR];


static int gic_routable_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
    return 0;
}

static void gic_routable_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
{

}

static int gic_routable_irq_domain_xlate(struct irq_domain *d,
                                         struct device_node *controller,
                                         unsigned int *intspec, unsigned int intsize,
                                         unsigned long *out_hwirq,
                                         unsigned int *out_type)
{
    *out_hwirq += 16;
    return 0;
}

static struct irq_domain_ops gic_default_routable_irq_domain_ops = {
    .map = gic_routable_irq_domain_map,
    .unmap = gic_routable_irq_domain_unmap,
    .xlate = gic_routable_irq_domain_xlate,
};

static int gic_irq_domain_map(struct irq_domain *d, unsigned int irq, irq_hw_number_t hw)
{
    struct gic_chip_data *gic = d->host_data;
    if (hw < 32)
    {

    }
    else
    {
        irq_domain_set_info(d, irq, hw, &gic->chip, d->host_data, handle_fasteoi_irq, NULL, NULL);
    }

    return 0;
}

static void gic_irq_domain_unmap(struct irq_domain *d, unsigned int irq)
{
	gic_routable_irq_domain_ops->unmap(d, irq);
}

static int gic_irq_domain_xlate(struct irq_domain *d,
				struct device_node *controller,
				 unsigned int *intspec, unsigned int intsize,
				unsigned long *out_hwirq, unsigned int *out_type)
{
    unsigned long ret = 0;

    if (d->of_node != controller)
        return -EINVAL;
    if (intsize < 3)
        return -EINVAL;

    /* Get the interrupt number and add 16 to skip over SGIs */
    *out_hwirq = intspec[1] + 16;

    /* For SPIs, we need to add 16 more to get the GIC irq ID number */
    if (!intspec[0])
    {
        ret = gic_routable_irq_domain_ops->xlate(d, controller,
                                                 intspec,
                                                 intsize,
                                                 out_hwirq,
                                                 out_type);

        if (IS_ERR_VALUE(ret))
            return ret;
    }

    *out_type = intspec[2] & IRQ_TYPE_SENSE_MASK;

    return ret;
}

static int gic_irq_domain_alloc(struct irq_domain *domain, unsigned int virq,
				unsigned int nr_irqs, void *arg)
{
	int i, ret;
	irq_hw_number_t hwirq;
	unsigned int type = IRQ_TYPE_NONE;
	struct of_phandle_args *irq_data = arg;

	ret = gic_irq_domain_xlate(domain, irq_data->np, irq_data->args,
				   irq_data->args_count, &hwirq, &type);
	if (ret)
		return ret;

	for (i = 0; i < nr_irqs; i++)
		gic_irq_domain_map(domain, virq + i, hwirq + i);


	return 0;
}

static inline void __iomem *gic_cpu_base(struct irq_data *d)
{
	struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
	return gic_data_cpu_base(gic_data);
}

static inline void __iomem *gic_dist_base(struct irq_data *d)
{
	struct gic_chip_data *gic_data = irq_data_get_irq_chip_data(d);
	return gic_data_dist_base(gic_data);
}

void irq_domain_free_irqs_top(struct irq_domain *domain, unsigned int virq,  unsigned int nr_irqs)
{
}

static struct irq_domain_ops gic_irq_domain_hierarchy_ops = {
	.xlate = gic_irq_domain_xlate,
	.alloc = gic_irq_domain_alloc,
	.free = irq_domain_free_irqs_top,
};

static  struct irq_domain_ops gic_irq_domain_ops = {
	.map = gic_irq_domain_map,
	.unmap = gic_irq_domain_unmap,
	.xlate = gic_irq_domain_xlate,
};


 struct irq_domain_ops *gic_routable_irq_domain_ops = &gic_default_routable_irq_domain_ops;

static unsigned int gic_irq(struct irq_data *d)
{
	return d->hwirq;
}

 static void gic_mask_irq(struct irq_data *d)
 {
     u32 mask = 1 << (gic_irq(d) % 32);
     //unsigned long flags;

     // raw_spin_lock_irqsave(&irq_controller_lock, flags);
     writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_CLEAR + (gic_irq(d) / 32) * 4);
     if (gic_arch_extn.irq_mask)
         gic_arch_extn.irq_mask(d);
     // raw_spin_unlock_irqrestore(&irq_controller_lock, flags);
 }

 static void gic_unmask_irq(struct irq_data *d)
 {
     u32 mask = 1 << (gic_irq(d) % 32);
     //unsigned long flags;

     // raw_spin_lock_irqsave(&irq_controller_lock, flags);
     if (gic_arch_extn.irq_unmask)
         gic_arch_extn.irq_unmask(d);
     writel_relaxed(mask, gic_dist_base(d) + GIC_DIST_ENABLE_SET + (gic_irq(d) / 32) * 4);
     // raw_spin_unlock_irqrestore(&irq_controller_lock, flags);
 }

 static void gic_eoi_irq(struct irq_data *d)
 {
     if (gic_arch_extn.irq_eoi)
     {
         // raw_spin_lock(&irq_controller_lock);
         gic_arch_extn.irq_eoi(d);
         // raw_spin_unlock(&irq_controller_lock);
     }

     writel_relaxed(gic_irq(d), gic_cpu_base(d) + GIC_CPU_EOI);
 }

 static int gic_set_type(struct irq_data *d, unsigned int type)
 {
     void __iomem *base = gic_dist_base(d);
     unsigned int gicirq = gic_irq(d);
     //unsigned long flags;
     int ret;

     /* Interrupt configuration for SGIs can't be changed */
     if (gicirq < 16)
         return -EINVAL;

     /* SPIs have restrictions on the supported types */
     if (gicirq >= 32 && type != IRQ_TYPE_LEVEL_HIGH &&
         type != IRQ_TYPE_EDGE_RISING)
         return -EINVAL;

     // raw_spin_lock_irqsave(&irq_controller_lock, flags);

     if (gic_arch_extn.irq_set_type)
         gic_arch_extn.irq_set_type(d, type);

     ret = gic_configure_irq(gicirq, type, base, NULL);

     // raw_spin_unlock_irqrestore(&irq_controller_lock, flags);

     return ret;
 }

 static int gic_retrigger(struct irq_data *d)
 {
     if (gic_arch_extn.irq_retrigger)
         return gic_arch_extn.irq_retrigger(d);

     /* the genirq layer expects 0 if we can't retrigger in hardware */
     return 0;
 }

 struct irq_chip gic_arch_extn = {
	.irq_eoi	= NULL,
	.irq_mask	= NULL,
	.irq_unmask	= NULL,
	.irq_retrigger	= NULL,
	.irq_set_type	= NULL,
	.irq_set_wake	= NULL,
};

struct irq_chip gic_chip = {
     .name = "GIC",
     .irq_mask = gic_mask_irq,
     .irq_unmask = gic_unmask_irq,
     .irq_eoi = gic_eoi_irq,
     .irq_set_type = gic_set_type,
     .irq_retrigger = gic_retrigger,
#ifdef CONFIG_SMP
     .irq_set_affinity = gic_set_affinity,
#endif
};

static void gic_handle_irq(struct pt_regs *regs)
{
    u32 irqstat, irqnr;
    struct gic_chip_data *gic = &gic_data[0];
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    do
    {
        irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
        irqnr = irqstat & GICC_IAR_INT_ID_MASK;

        if (irqnr > 15 && irqnr < 1021)
        {
            writel_relaxed(irqnr, cpu_base +  0x10); // end of irq register eoir
            handle_domain_irq(gic->domain, irqnr, regs);
            continue;
        }
        if (irqnr < 16)
        {
            writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
            handle_IPI(irqnr, regs);
#endif
            continue;
        }
        break;
    } while (1);
}

static void gic_dist_init(struct gic_chip_data *gic)
{
    unsigned int i;
    unsigned int irqRegs = 0;
    void __iomem *dist_base = gic_data_dist_base(gic);

    irqRegs = readl_relaxed(dist_base + 0x4); // interrupt controller type register
    irqRegs = (irqRegs & 0x1fUL) + 1;

    for (i = 0; i < irqRegs; i++)
        writel_relaxed(0xffffffffUL, ((unsigned int *)(dist_base + 0x180)) + i); // clear-enable register

    writel_relaxed(0x1, dist_base + 0); // distributor control register
}

static void gic_cpu_init(struct gic_chip_data *gic)
{
    void __iomem *cpu_base = gic_data_cpu_base(gic);

    writel_relaxed(0xffUL << (8 -5), cpu_base + 0x4); // interrupt priority mask register
    writel_relaxed(7 - 5, cpu_base + 0x8); //  binary point register
    writel_relaxed(1, cpu_base + 0); // cpu interface control register
}

static void gic_pm_init(struct gic_chip_data *gic)
{

}


 /*GIC（通用中断控制器）初始化的核心函数。参数分别为 GIC 编号、起始中断号、分发器基地址、CPU 接口基地址、每 CPU 偏移量、设备树节点。 */
 void __init gic_init_bases(unsigned int gic_nr, int irq_start, void __iomem *dist_bast, void __iomem *cpubase, unsigned int percpu_offset, struct device_node *node)
 {

     irq_hw_number_t hwirq_base;
     struct gic_chip_data *gic;
     int gic_irqs, irq_base, i;
     unsigned int nr_routable_irqs;

     // BUG_ON(gic_nr >= MAX_GIC_NR);

     gic = &gic_data[gic_nr];
#ifdef CONFIG_GIC_NON_BANKED
     if (percpu_offset)
     {
     }
     else
#endif
     {
     }

     /*始化 GIC 的 CPU 接口映射表，初始值全部为 0xff，后续每个 CPU 会探测并修正*/
     for (i = 0; i < NR_GIC_CPU_IF; i++)
         gic_cpu_map[i] = 0xff;

    gic->cpu_base.common_base = cpubase;
    gic->dist_base.common_base = dist_bast;

     /*
      * Find out how many interrupts are supported.
      * The GIC only supports up to 1020 interrupt sources.
      */
     gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
     gic_irqs = (gic_irqs + 1) * 32;
     if (gic_irqs > 1020)
         gic_irqs = 1020;
     gic->gic_irqs = gic_irqs;
     gic->chip = gic_chip;

     set_handle_irq(gic_handle_irq);

     if (node)
     { /* DT case */
          struct irq_domain_ops *ops = &gic_irq_domain_hierarchy_ops;

         if (!of_property_read_u32(node, "arm,routable-irqs", &nr_routable_irqs))
         {
             ops = &gic_irq_domain_ops;
             gic_irqs = nr_routable_irqs;
         }

         gic->domain = irq_domain_add_linear(node, gic_irqs, ops, gic);
         gic->domain->flags |= IRQ_DOMAIN_FLAG_HIERARCHY;
     }
     else
     { /* Non-DT case */
         /*
          * For primary GICs, skip over SGIs.
          * For secondary GICs, skip over PPIs, too.
          */
         if (gic_nr == 0 && (irq_start & 31) > 0)
         {
             hwirq_base = 16;
             if (irq_start != -1)
                 irq_start = (irq_start & ~31) + 16;
         }
         else
         {
             hwirq_base = 32;
         }

         gic_irqs -= hwirq_base; /* calculate # of irqs to allocate */

         irq_base = irq_alloc_descs(irq_start, 16, gic_irqs, -1);
         if (IS_ERR_VALUE(irq_base))
         {
             WARN(1, "Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n", irq_start);
             irq_base = irq_start;
         }

         gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base, hwirq_base, &gic_irq_domain_ops, gic);
     }

     if (WARN_ON(!gic->domain))
         return;

     if (gic_nr == 0)
     {
#ifdef CONFIG_SMP
         set_smp_cross_call(gic_raise_softirq);
         register_cpu_notifier(&gic_cpu_notifier);
#endif
         set_handle_irq(gic_handle_irq);
     }

     gic_chip.flags |= gic_arch_extn.flags;
     gic_dist_init(gic);
     gic_cpu_init(gic);
     gic_pm_init(gic);
 }

 static int gic_cnt = 0;
 static int __init gic_of_init(struct device_node *node, struct device_node *parent)
 {
     void __iomem *cpu_base;
     void __iomem *dist_base;
     u32 percpu_offset;
     //int irq;

     if (WARN_ON(!node))
         return -ENODEV;

     dist_base = of_iomap(node, 0);
     WARN(!dist_base, "unable to map gic dist registers\n");

     cpu_base = of_iomap(node, 1);
     WARN(!cpu_base, "unable to map gic cpu registers\n");

     if (of_property_read_u32(node, "cpu-offset", &percpu_offset))
         percpu_offset = 0;

     gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);

     if (parent)
     {
         //irq = irq_of_parse_and_map(node, 0);
         //gic_cascade_irq(gic_cnt, irq);
     }

     gic_cnt++;
     return 0;
 }

 IRQCHIP_DECLARE(cortex_a7_gic, "arm,cortex-a7-gic", gic_of_init);